Skip to content

feat(edgeauth): verify guard's identity JWT in Go and TypeScript (COR-1076) - #6

Open
malpou wants to merge 1 commit into
mainfrom
malthe/cor-1076-shared-identity-verifier-platform-kit-edgeauth-and-a-ts
Open

malpou wants to merge 1 commit into
mainfrom
malthe/cor-1076-shared-identity-verifier-platform-kit-edgeauth-and-a-ts

Conversation

@malpou

@malpou malpou commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Summary

The shared verifier for guard's identity JWT (contract COR-1073 §2, §3, §10), in Go and TypeScript, with conformance vectors both run.

  • edgeauth/: Verifier with ES256 only, typ bastion-id+jwt (compared the RFC 7515 / jose way: case-insensitive, application/ optional), a known kid, iss bastion, aud in Config.Audiences, exp/iat (and nbf when present) with 10 s leeway, sub required. The JWKS is fetched lazily (never in New) and cached 5 min. An unknown kid triggers at most one refetch per 30 s, and fetches are single-flight. A failed fetch is remembered for 5 s, and keys that were cached before still verify during an outage. go-jose v4 does the JWS parsing and signature check (it was already in the module graph); the claim rules are written out by hand so they match the TS side exactly.
  • Middleware: no header → anonymous. ErrInvalid → 401, logged through WithLogger. ErrUnavailable → 503. Bodies are plain text and never echo the token. RequireIdentity, RequireRole (any of; 401/403), FromContext/ContextWith, Identity.HasRole/HasAnyRole.
  • edgeauth/edgeauthtest: NewIssuer(t) (in-memory P-256 key, httptest JWKS closed by t.Cleanup), Config, Mint, MintWith(id, aud, func(*Claims)) for expired or wrong-aud tokens, Sign (aud = r.Host, lower case, no port).
  • edgeauth/testdata/vectors.json: 32 cases generated by go generate ./edgeauth (internal/vectorgen). The JWS is assembled by hand with deterministic RFC 6979 signatures, and a test fails when the committed file is stale. Cases: valid session/bearer, empty/absent roles, leeway edges for exp and iat, future iat/nbf, wrong/upper-case aud, aud array, wrong iss, HS256 key-confusion, alg none, wrong/missing typ, unknown/missing kid, foreign-key signature, DER signature, tampered payload, wrong claim types, missing sub/exp/iat, a non-JWS, JWKS unreachable → unavailable.
  • ts/edgeauth/ + root package.json (@metsaapp/edgeauth): createVerifier({jwksUrl?, audiences, now?}) → {verify, fromHeaders}, built on jose (createRemoteJWKSet with cacheMaxAge 5 min, cooldown 30 s, timeout 5 s; jwtVerify with ES256, typ, issuer, audience, clockTolerance 10, plus the iat-not-ahead check jose skips). Errors are EdgeAuthInvalid / EdgeAuthUnavailable.
  • CI job edgeauth-ts: runs npm ci, checks the jose ranges agree, type-checks, checks the committed dist matches the source, then runs vitest under Node and under Bun.
  • README: edgeauth section (rules, wiring for net/http and echo, test minting, TS install), and PLATFORM_KIT_EDGEAUTH_* added to the runtime contract's variables.

API additions beyond the contract (nothing renamed)

ConfigFromEnv(), ErrConfig, WithLogger, consts Issuer, TokenType, DefaultJWKSURL, Leeway, SourceSession/SourceBearer; edgeauthtest.MintWith, Claims, JWKSURL(), KeyID, Lifetime. NewIssuer takes testing.TB. RequireRole() with no roles behaves like RequireIdentity. Interpretation: "iat not more than 60 s ahead" is implemented as iat ≤ now + 10 s leeway (guard sets iat = now).

Packaging decisions

  • Committed dist: Bun and pnpm do not run lifecycle scripts of git dependencies, so a prepare build is unreliable. Bun imports the TS source through the bun export condition. Node and bundlers get dist/index.js + .d.ts. files is limited to ts/edgeauth src/dist. I installed this commit with npm install @metsaapp/edgeauth@git+file://…#<sha> and bun add and imported it under both runtimes.
  • Dev tooling (ts/edgeauth/package.json, lockfile, node_modules) stays under ts/. A stub ts/go.mod keeps ./..., golangci-lint and the Go module zip out of it. The root package.json has no devDependencies.
  • release-please: the root package.json version is in extra-files, so it bumps with each Go release. The release type stays go.

Test evidence

  • go build ./... && go vet ./... && go test -race ./...: ok (edgeauth, natsclient, runtime)
  • go run github.com/golangci/golangci-lint/v2/cmd/[email protected] run: 0 issues
  • npm run check && npm run typecheck && npm run build && npm test: 37 passed. bun --bun x vitest run: 37 passed. dist matches the source.

Apps pin a pseudo-version of this branch until it is released (go get github.com/MetsaApp/platform-kit@338c1157e883d35133bfc66b537420da61267397, which resolves to v0.2.1-0.20261030231032-338c1157e883); release-please cuts v0.3.0 on merge.


Generated by Claude Code

…-1076)

edgeauth verifies the x-bastion-identity JWT guard sends upstream (ES256,
typ bastion-id+jwt, iss bastion, aud in the app's hosts, exp/iat with 10 s
leeway) against guard's JWKS, fetched lazily, cached 5 min and refetched
for an unknown kid at most once per 30 s. Middleware puts the identity in
the context, answers 401 for an invalid token and 503 when the keys are
unreachable; RequireIdentity and RequireRole refuse anonymous and
unauthorised callers. edgeauthtest mints identities for app tests.

@metsaapp/edgeauth is the TypeScript twin on jose, installable from this
repository by tag. Both run the conformance vectors in
edgeauth/testdata/vectors.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MwB9uUtkK8BFT7ZGoXwX9u
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants